package org.simple.framework.data.jpa.query;

import org.simple.framework.data.annotation.QueryProperty;
import org.simple.framework.data.entity.Identifiable;
import org.simple.framework.data.query.BaseQuery;
import org.simple.framework.data.query.Junction;
import org.simple.framework.data.query.Order;
import org.simple.framework.data.query.QuerySpecification;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import jakarta.persistence.criteria.*;
import jakarta.persistence.metamodel.EntityType;
import java.util.*;

/**
 * 项目名称：simple-framework
 * 类名称：JpaQuerySpecification
 * 类描述：JpaQuerySpecification
 * 创建时间：2018/9/6
 *
 * @author sisyphus   (E-mail:1620657419@qq.com)
 * @version v1.0
 */
public class JpaQuerySpecification<T extends Identifiable> extends QuerySpecification<T> implements Specification<T> {

    private JpaExpressionHandler jpaExpressionHandler;

    private BaseQuery query;

    public JpaQuerySpecification(BaseQuery query) {
        super(query);
        this.query = query;
        JpaMatchHandler jpaMatchHandler = new JpaMatchHandler();
        this.jpaExpressionHandler = new JpaExpressionHandler(jpaMatchHandler);
    }

    private static Map<Root, Map<String, Path>> pathCache = new ConcurrentReferenceHashMap<>();



    @Override
    public Predicate toPredicate(Root<T> entityRoot, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
        List<Order> queryOrders = getClassOrders();
        if (queryOrders.size() != 0) {
            queryOrders.forEach(order -> {
                if (order.getDirection().isAsc()) {
                    criteriaQuery.orderBy(criteriaBuilder.asc(getPath(order.getColumn(), entityRoot)));
                } else {
                    criteriaQuery.orderBy(criteriaBuilder.desc(getPath(order.getColumn(), entityRoot)));
                }
            });
        }

        List<Predicate> allPredicates = new ArrayList<>();
        Map<String, List<Predicate>> groupPredicates = new HashMap<>();

        ReflectionUtils.doWithFields(query.getClass(), field -> {
            QueryProperty queryProperty = field.getAnnotation(QueryProperty.class);

            ReflectionUtils.makeAccessible(field);
            Object value = field.get(query);
            if (value != null) {
                if (StringUtils.hasText(queryProperty.group())) {
                    List<Predicate> predicates = groupPredicates.computeIfAbsent(queryProperty.group(), k -> new ArrayList<>());
                    predicates.add(buildPredicate(criteriaBuilder, entityRoot, queryProperty, value));
                } else {
                    allPredicates.add(buildPredicate(criteriaBuilder, entityRoot, queryProperty, value));
                }

            }
            field.setAccessible(false);
        }, field -> field.isAnnotationPresent(QueryProperty.class));

        Predicate result = null;
        Junction classJunction = getClassJunction();
        if (Junction.AND.equals(classJunction)) {
            groupPredicates.keySet().forEach(s -> allPredicates.add(criteriaBuilder.or(groupPredicates.get(s).toArray(new Predicate[0]))));
            result = criteriaBuilder.and(allPredicates.toArray(new Predicate[0]));
        } else if (Junction.OR.equals(classJunction)) {
            groupPredicates.keySet().forEach(s -> allPredicates.add(criteriaBuilder.and(groupPredicates.get(s).toArray(new Predicate[0]))));
            result = criteriaBuilder.or(allPredicates.toArray(new Predicate[0]));
        }

        return result;
    }

    private Predicate buildPredicate(CriteriaBuilder criteriaBuilder, Root<T> entityRoot, QueryProperty queryProperty, Object value) {
        if (value.getClass().isArray() || Collection.class.isInstance(value)) {
            //to support iterable value
            if (!isExpressionSupportIterableValue(queryProperty.expression())) {
                List<Predicate> predicates = new ArrayList<>();
                if (value.getClass().isArray()) {
                    Arrays.asList((Object[]) value).forEach(o -> predicates.add(jpaExpressionHandler.handle(queryProperty.expression(), queryProperty.match()).build(criteriaBuilder, getPath(queryProperty.column(), entityRoot), o)));
                } else if (Collection.class.isInstance(value)) {
                    ((Collection<?>) value).forEach(o -> predicates.add(jpaExpressionHandler.handle(queryProperty.expression(), queryProperty.match()).build(criteriaBuilder, getPath(queryProperty.column(), entityRoot), o)));
                }
                return criteriaBuilder.or(predicates.toArray(new Predicate[0]));
            }
        }
        return jpaExpressionHandler.handle(queryProperty.expression(), queryProperty.match())
                .build(criteriaBuilder, getPath(queryProperty.column(), entityRoot), value);
    }


    private Path getPath(String columnName, Root<T> entityRoot) {
        EntityType<T> entityType = entityRoot.getModel();
        Map<String, Path> pathMap = pathCache.get(entityRoot);
        Path path;
        if (pathMap == null) {
            pathMap = new HashMap<>();
            pathCache.put(entityRoot, pathMap);
        }
        if (pathMap.get(columnName) == null) {
            if (columnName.indexOf(".") > 0) { // 带Join查询
                String[] columns = columnName.split("\\.");
                From from = entityRoot;
                for (int i = 0; i < columns.length - 1; i++) {// 不包含最后一位
                    from = from.join(columns[i]);
                }
                path = from.get(columns[columns.length - 1]);
            } else {
                path = entityRoot.get(entityType.getSingularAttribute(columnName));
            }
            pathMap.put(columnName, path);
        }
        return pathMap.get(columnName);
    }


}
